

#include "Program.h"
#include <stdexcept>
#include "../internal/engine_constants.h"

namespace z3d {

    Program::Program(std::vector<z3d::ShaderItem>& shaders) :
        _object(0)
    {
        _log = &glog::CGlobalLog::getSingleton();
        
        if(shaders.size() <= 0) {
            THROW_AND_LOG(Z3D_APPENDER, z3d::core::Z3DRuntimeException, "No shaders were provided to create the program");
        }
        
        //create the program object
        _object = glCreateProgram();
        if(_object == 0) {
            THROW_AND_LOG(Z3D_APPENDER, z3d::core::Z3DRuntimeException, "glCreateProgram failed");
        }

        //attach all the shaders
        for(unsigned i = 0; i < shaders.size(); ++i) {
            shaders[i].init();
            shaders[i].load();
            glAttachShader(_object, shaders[i].getHandle());
        }


        //link the shaders together
        glLinkProgram(_object);

        //detach all the shaders
        for(unsigned i = 0; i < shaders.size(); ++i)
            glDetachShader(_object, shaders[i].getHandle());

        //throw exception if linking failed
        GLint status;
        glGetProgramiv(_object, GL_LINK_STATUS, &status);
        if (status == GL_FALSE) {
            std::string msg("Program linking failure: ");

            GLint infoLogLength;
            glGetProgramiv(_object, GL_INFO_LOG_LENGTH, &infoLogLength);
            char* strInfoLog = new char[infoLogLength + 1];
            glGetProgramInfoLog(_object, infoLogLength, NULL, strInfoLog);
            msg += strInfoLog;
            delete[] strInfoLog;

            glDeleteProgram(_object); _object = 0;
            THROW_AND_LOG(Z3D_APPENDER, z3d::core::Z3DRuntimeException, msg.c_str());
        }
    }

    Program::~Program() {
        //might be 0 if ctor fails by throwing exception
        if(_object != 0) glDeleteProgram(_object);
    }

    GLuint Program::object() const {
        return _object;
    }

    GLint Program::attrib(const GLchar* attribName) const {
        if(!attribName)
            THROW_AND_LOG(Z3D_APPENDER, z3d::core::Z3DRuntimeException, "attribName was NULL");

        GLint attrib = glGetAttribLocation(_object, attribName);
        if(attrib == -1) {
            std::string msg("Program attribute not found: ");
            msg += attribName;
            THROW_AND_LOG(Z3D_APPENDER, z3d::core::Z3DRuntimeException, msg.c_str());
        }
        return attrib;
    }

    GLint Program::uniform(const GLchar* uniformName) const {
        if(!uniformName)
            THROW_AND_LOG(Z3D_APPENDER, z3d::core::Z3DRuntimeException, "uniformName was NULL");

        GLint uniform = glGetUniformLocation(_object, uniformName);
        if(uniform == -1) {
            std::string msg("Program uniform not found: ");
            msg += uniformName;
            THROW_AND_LOG(Z3D_APPENDER, z3d::core::Z3DRuntimeException, msg.c_str());
        }
        return uniform;
    }
}